#pragma once

#include <boost/numeric/conversion/cast.hpp>
#include <boostext/util/make_shared.hpp>
#include <boost/function.hpp>
#include <boostext/typedefs.hpp>
#include <boostext/exception.hpp>
#include <engine.core/vertices.hpp>

#include "exports.hpp"
#include "Buffer.hpp"




namespace Engine
{
	namespace Video
	{
		struct ENGINE_VIDEO_API Primitive
		{
			enum Type
			{
				IndexedTriangles,
				IndexedLines,
				UnindexedTriangles,
				UnindexedLines,
			};
		};
		///////////////////////////////////////////////////////////////////////////////////////////////////




		class ENGINE_VIDEO_API Mesh
			: public Buffer
		{
		public:

			SHARED(Mesh);

		public:

			/**
			 * Get the type of primitive, this mesh is going to render.
			 */
			virtual Primitive::Type primitiveType() const = 0;



			/**
			 * These functions update the Mesh with the given set of vertices and indices.
			 * It is ok to change the index type during runtime (there's no need to create a new mesh for this),
			 * however changing the vertex type is not allowed.
			 *
			 * Throws an exception when this is a static mesh.
			 */
			template <typename Vertex, typename Index>
			void update(const std::vector<Vertex>& vertices, const std::vector<Index>& indices)
			{
				// Verify that the user didn't mess it up
				Mesh::verifyMeshData(primitiveType(), vertices, indices);

				// Fetch the VertexLayout for this vertex-type
				// And get a pointer to both buffers (when possible)
				Format::Type   indexType  = Mesh::indexType<Index>();
				uint32         vertexSize = boost::numeric_cast<uint32>(vertices.size()) * sizeof(Vertex);
				const void*    vertexData = vertexSize ? &vertices[0] : 0;
				uint32         indexSize  = boost::numeric_cast<uint32>(indices.size()) * sizeof(Index);
				const void*    indexData  = indexSize ? &indices[0] : 0;

				if(reflect() != Vertex::reflect())
					BOOSTEXT_THROW(programming_error("It's not allowed to update a Mesh with another vertex type than it was created with"));

				// Now create a mesh from that raw data
				return update(vertexData, vertexSize, indexData, indexSize, sizeof(Index), indexType);
			}

			template <typename Index> static Format::Type indexType();
			template <> static Format::Type indexType<uint16>() { return Format::R16_UINT; }
			template <> static Format::Type indexType<uint32>() { return Format::R32_UINT; }

		protected:

			/**
			 * These functions update the Mesh with the given set of vertices and indices.
			 * Throws an exception when this is a static mesh.
			 */
			virtual void update(const void* vertexData, uint32 vertexSize, const void* indexData, uint32 indexSize, uint32 sizePerIndex, Format::Type indexType) = 0;

			/**
			 * Get the size of one vertex of this mesh.
			 */
			virtual uint32 sizePerVertex() const = 0;

			/**
			 * Get the size of one index of this mesh.
			 */
			virtual uint32 sizePerIndex() const = 0;

			/**
			 * Calculate the number of primitives for the given vertices, indices
			 * and primitive type.
			 */
			static uint32 calculatePrimitiveCount(uint32 numVertices, uint32 numIndices, Primitive::Type primitive)
			{
				switch(primitive)
				{
				case Primitive::IndexedTriangles:
					{
						uint32 primitives = numIndices / 3;
						return primitives;
					}
				case Primitive::IndexedLines:
					{
						uint32 primitives = numIndices / 2;
						return primitives;
					}
				case Primitive::UnindexedTriangles:
					{
						uint32 primitives = numVertices / 3;
						return primitives;
					}
				case Primitive::UnindexedLines:
					{
						uint32 primitives = numVertices / 2;
						return primitives;
					}
				default: BOOSTEXT_THROW(boostext::invalid_value_exception(primitive));
				}
			}

			/**
			 * Verify if the given data is valid, when rendered as the given primitive type.
			 */
			template <typename VertexType, typename IndexType>
			static void verifyMeshData(Primitive::Type primitive, const std::vector<VertexType>& vertices, const std::vector<IndexType>& indices)
			{
				using namespace boostext;

				if(vertices.size() > std::numeric_limits<uint32>::max())
					BOOSTEXT_THROW(programming_error("64bit sized vertexbuffers are not supported, please use a lower vertex count per mesh"));

				if(indices.size() > std::numeric_limits<uint32>::max())
					BOOSTEXT_THROW(programming_error("64bit sized indexbuffers are not supported, please use a lower vertex count per mesh"));



				switch(primitive)
				{
				case Primitive::IndexedTriangles:
					{
						// The following conditions have to be met:
						// - t * 3 = N, N being the number of indices, t a natural number
						if(indices.size() % 3 != 0)
							BOOSTEXT_THROW(critical_exception(format("An Indexed TriangleList layout requires the indexbuffer to have a multiple of 3 entries, but it has '%1%'") % indices.size()));
					}
					break;
				case Primitive::IndexedLines:
					{
						// The following conditions have to be met:
						// - t * 2 = N, N being the number of indices, t a natural number
						if(indices.size() % 2 != 0)
							BOOSTEXT_THROW(critical_exception(format("An Indexed LineList layout requires the indexbuffer to have a multiple of 2 entries, but it has '%1%'") % indices.size()));
					}
					break;
				case Primitive::UnindexedTriangles:
					{
						// The following conditions have to be met:
						// - t * 3 = N, N being the number of vertices, t a natural number
						// - the indexbuffer must be empty
						if(vertices.size() % 3 != 0)
							BOOSTEXT_THROW(critical_exception(format("The vertexbuffer must contain a multiple of 3 entries for the Unindexed TriangleList primitive, but contains '%1' vertices") % vertices.size()));
						if(indices.size())
							BOOSTEXT_THROW(critical_exception(format("The indexbuffer must not contain data for the Unindexed TriangleList primitive") % indices.size()));
					}
					break;
				case Primitive::UnindexedLines:
					{
						// The following conditions have to be met:
						// - t * 3 = N, N being the number of vertices, t a natural number
						// - the indexbuffer must be empty
						if(vertices.size() % 2 != 0)
							BOOSTEXT_THROW(critical_exception(format("The vertexbuffer must contain a multiple of 2 entries for the Unindexed LineList primitive, but contains '%1' vertices") % vertices.size()));
						if(indices.size())
							BOOSTEXT_THROW(critical_exception(format("The indexbuffer must not contain data for the Unindexed LineList primitive") % indices.size()));
					}
					break;
				default:
					BOOSTEXT_THROW(invalid_value_exception(primitive));
				}


				#ifdef _DEBUG
				switch(primitive)
				{
				case Primitive::IndexedTriangles:
				case Primitive::IndexedLines:
					// Indexed primitives: bounds checking needed
					foreach(IndexType i, indices)
					{
						if(i < 0 || i >= vertices.size())
							BOOSTEXT_THROW(critical_exception("Indexbuffer contains invalid index that points beyond the vertexbuffer's size"));
					}
					break;
				case Primitive::UnindexedTriangles:
				case Primitive::UnindexedLines:
					// Unindexed primitives: no checking
					break;
				default:
					BOOSTEXT_THROW(invalid_value_exception(primitive));
				}
				#endif
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
